package cn.icanci.loopstack.ddk.client.repository;

import cn.hutool.http.Method;
import cn.hutool.json.JSONUtil;
import cn.icanci.loopstack.api.client.Client;
import cn.icanci.loopstack.api.client.http.HttpClientImpl;
import cn.icanci.loopstack.ddk.client.exception.*;
import cn.icanci.loopstack.ddk.client.properties.DdkProperties;
import cn.icanci.loopstack.ddk.client.server.NamedNettyServerHandler;
import cn.icanci.loopstack.ddk.common.anno.*;
import cn.icanci.loopstack.ddk.common.result.R;
import cn.icanci.loopstack.ddk.common.socket.UriConstant;
import cn.icanci.loopstack.ddk.common.socket.load.DictValueObjectDTO;
import cn.icanci.loopstack.ddk.common.socket.load.ResourceLoadRequestDTO;
import cn.icanci.loopstack.ddk.common.socket.load.ResourceLoadResponseDTO;
import cn.icanci.loopstack.ddk.common.socket.load.ResourceValueObjectDTO;
import cn.icanci.loopstack.ddk.common.socket.publish.PublishMethodInvokeDTO;
import cn.icanci.loopstack.ddk.common.socket.publish.PublishValueRefreshDTO;
import io.netty.util.internal.ThrowableUtil;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * DdkRepositoryHolder
 * 本地仓储缓存器
 * 
 * @author icanci
 * @since 1.0 Created in 2023/01/06 22:53
 */
@Service
public class DdkRepositoryHolder implements InitializingBean {

    private static final Logger                           logger                         = LoggerFactory.getLogger(DdkRepositoryHolder.class);

    private static final Client                           CLIENT                         = HttpClientImpl.getInstance();

    @Resource
    private DdkProperties                                 ddkProperties;

    @Resource
    private ApplicationContext                            applicationContext;

    private static final String                           POUND_SIGN                     = "#";
    /**
     * 字段支持处理的类型
     */
    private static final Set<Class<?>>                    FIELD_SUPPORT_CLASS_TYPES      = Sets.newHashSet(String.class, Integer.class, int.class, Long.class, long.class,
        Double.class, double.class, Float.class, float.class, Boolean.class, boolean.class, Character.class, char.class, Short.class, short.class, Byte.class, byte.class,
        BigDecimal.class);
    /**
     * 字段支持处理的类型名字
     */
    private static final String                           FIELD_SUPPORT_CLASS_TYPE_NAMES = String.join(",",
        Sets.newHashSet(String.class.getName(), Integer.class.getName(), int.class.getName(), Long.class.getName(), long.class.getName(), Double.class.getName(),
            double.class.getName(), Float.class.getName(), float.class.getName(), Boolean.class.getName(), boolean.class.getName(), Character.class.getName(), char.class.getName(),
            Short.class.getName(), short.class.getName(), Byte.class.getName(), byte.class.getName(), BigDecimal.class.getName()));
    /**
     * http://{address}:port+UriConstant.ToClient.LOAD
     */
    private static final String                           INVOKE_REQUEST_FORMAT          = "http://%s:%s" + UriConstant.ToServer.LOAD;
    /**
     * 字段map
     * key: 配置key
     * value: FieldHolder
     */
    private static final Map<FieldHolderKey, FieldHolder> FIELD_MAP                      = new ConcurrentHashMap<>();

    /**
     * 方法map
     * key: resource
     * value: MethodHolder
     */
    private static final Map<String, MethodHolder>        METHOD_MAP                     = new ConcurrentHashMap<>();

    @Override
    public void afterPropertiesSet() throws Exception {
        // 1.加载缓存
        toRefreshCache();
        // 2.加载数据
        toLoad();
        // 3.注入对象
        NamedNettyServerHandler.setDdkRepositoryHolder(this);
    }

    /**
     * 加载缓存Map
     */
    private void toRefreshCache() {
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            Object bean = applicationContext.getBean(beanName);
            // 注入字段到map
            injectFields(bean);
            // 注入方法到map
            injectMethods(bean);
        }
    }

    /**
     * 注入字段到map
     * 
     * @param bean bean
     */
    private void injectFields(Object bean) {
        Field[] fields = FieldUtils.getAllFields(bean.getClass());

        for (Field field : fields) {
            DdkValue ddkValue = field.getAnnotation(DdkValue.class);
            if (ddkValue == null) {
                continue;
            }
            String value = ddkValue.value();
            // data 
            if (StringUtils.isBlank(value)) {
                throw new DdkValueNotFoundException(
                    "In Class: " + bean.getClass().getSimpleName() + ", filed: " + field.getName() + ",the @DdkValue's value is Empty, Please Check it!");
            }
            if (!StringUtils.startsWith(value, POUND_SIGN)) {
                throw new DdkValueNotFoundException(
                    "In Class: " + bean.getClass().getSimpleName() + ", filed: " + field.getName() + ",the @DdkValue's value is Not Start with '#', Please Check it!");

            }
            String reallyValue = StringUtils.substringAfter(value, POUND_SIGN).trim();
            if (StringUtils.isBlank(reallyValue)) {
                throw new DdkValueNotFoundException("In Class: " + bean.getClass().getSimpleName() + ", filed: " + field.getName()
                                                    + ",the @DdkValue's value is start with '#', but the config value is Empty, Please Check it!");
            }
            Class<?> clazz = field.getType();
            if (!FIELD_SUPPORT_CLASS_TYPES.contains(clazz)) {
                throw new DdkValueIllegalityException(
                    "In Class: " + bean.getClass().getSimpleName() + ", filed: " + field
                        .getName() + ",the @DdkValue's value is start with '#', and the config value is not Empty,but the type of field is not Support,the Support type are: "
                                                      + FIELD_SUPPORT_CLASS_TYPE_NAMES + ", Please Check it!");

            }

            FieldHolder fieldHolder = new FieldHolder();
            fieldHolder.setField(field);
            fieldHolder.setFieldClass(clazz);
            fieldHolder.setValue(StringUtils.EMPTY);
            fieldHolder.setBean(bean);
            fieldHolder.setDdkKey(reallyValue);

            FIELD_MAP.put(new FieldHolderKey(reallyValue, clazz), fieldHolder);
        }
    }

    /**
     * 注入到方法 map
     * 
     * @param bean bean
     */
    private void injectMethods(Object bean) {
        DdkResource ddkResource = AopUtils.getTargetClass(bean).getAnnotation(DdkResource.class);
        if (ddkResource == null) {
            return;
        }

        String ddkResourceValue = ddkResource.value();
        if (StringUtils.isBlank(ddkResourceValue)) {
            throw new DdkResourceNotFoundException("In Class: " + bean.getClass().getSimpleName() + ",the @DdkResource's value is Empty, Please Check it!");
        }
        if (!StringUtils.startsWith(ddkResourceValue, POUND_SIGN)) {
            throw new DdkResourceNotFoundException("In Class: " + bean.getClass().getSimpleName() + ",the @DdkResource's value is Not Start with '#', Please Check it!");

        }
        String reallyResourceValue = StringUtils.substringAfter(ddkResourceValue, POUND_SIGN).trim();
        if (StringUtils.isBlank(reallyResourceValue)) {
            throw new DdkResourceNotFoundException(
                "In Class: " + bean.getClass().getSimpleName() + ",the @DdkResource's value is start with '#', but the config value is Empty, Please Check it!");
        }
        java.lang.reflect.Method[] declaredMethods = bean.getClass().getDeclaredMethods();

        java.lang.reflect.Method beforeCall = null;
        java.lang.reflect.Method call = null;
        java.lang.reflect.Method afterCall = null;

        for (java.lang.reflect.Method method : declaredMethods) {
            beforeCall = getBeforeCall(bean, beforeCall, method);
            call = getCall(bean, call, method);
            afterCall = getAfterCall(bean, afterCall, method);
        }
        if (call == null) {
            throw new DdkResourceIllegalityException("In Class: " + bean.getClass().getSimpleName() + ", the @DdkCall's Method is null! , Please Check it!");
        }

        MethodHolder repeat = METHOD_MAP.get(reallyResourceValue);
        if (repeat != null) {
            throw new DdkResourceIllegalityException("In Class: " + bean.getClass().getSimpleName() + ", the @DdkResource's value is repeat! , Please Check it!");
        }
        MethodHolder methodHolder = new MethodHolder();
        methodHolder.setResource(reallyResourceValue);
        methodHolder.setValue(StringUtils.EMPTY);
        methodHolder.setClazz(bean.getClass());
        methodHolder.setBean(bean);

        methodHolder.setBeforeMethodCall(beforeCall);
        methodHolder.setMethodCall(call);
        methodHolder.setAfterMethodCall(afterCall);

        METHOD_MAP.put(reallyResourceValue, methodHolder);
    }

    /**
     * getBeforeCall
     * 
     * @param bean bean
     * @param beforeCall beforeCall
     * @param method method
     * @return beforeCall
     */
    private java.lang.reflect.Method getBeforeCall(Object bean, java.lang.reflect.Method beforeCall, java.lang.reflect.Method method) {
        if (method.getAnnotation(DdkBeforeCall.class) != null) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length != 1) {
                throw new DdkResourceIllegalityException("In Class: " + bean.getClass().getSimpleName() + ", method: " + method.getName()
                                                         + ", the @DdkBeforeCall's parameterTypes is not equal to one , Please Check it!");
            }
            if (parameterTypes[0] != String.class) {
                throw new DdkResourceIllegalityException("In Class: " + bean.getClass().getSimpleName() + ", method: " + method.getName()
                                                         + ", the @DdkBeforeCall's parameterTypes is not equal 'java.lang.String' , Please Check it!");
            }
            if (beforeCall != null) {
                throw new DdkResourceIllegalityException(
                    "In Class: " + bean.getClass().getSimpleName() + ", method: " + method.getName() + ", the @DdkBeforeCall's was accessed by " + beforeCall.getName()
                                                         + ", but now found new one, the method be modified @DdkBeforeCall only has one , Please Check it!");
            } else {
                beforeCall = method;
            }
        }
        return beforeCall;
    }

    /**
     * getCall
     *
     * @param bean bean
     * @param call call
     * @param method method
     * @return call
     */
    private java.lang.reflect.Method getCall(Object bean, java.lang.reflect.Method call, java.lang.reflect.Method method) {
        if (method.getAnnotation(DdkCall.class) != null) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length != 1) {
                throw new DdkResourceIllegalityException(
                    "In Class: " + bean.getClass().getSimpleName() + ", method: " + method.getName() + ", the @DdkCall's parameterTypes is not equal to one , Please Check it!");
            }
            if (parameterTypes[0] != String.class) {
                throw new DdkResourceIllegalityException("In Class: " + bean.getClass().getSimpleName() + ", method: " + method.getName()
                                                         + ", the @DdkCall's parameterTypes is not equal 'java.lang.String' , Please Check it!");
            }
            if (call != null) {
                throw new DdkResourceIllegalityException("In Class: " + bean.getClass().getSimpleName() + ", method: " + method.getName() + ", the @DdkCall's was accessed by "
                                                         + call.getName() + ", but now found new one, the method be modified @DdkCall only has one , Please Check it!");
            } else {
                call = method;
            }
        }
        return call;
    }

    /**
     * getAfterCall
     * 
     * @param bean bean
     * @param afterCall afterCall
     * @param method method
     * @return afterCall
     */
    private java.lang.reflect.Method getAfterCall(Object bean, java.lang.reflect.Method afterCall, java.lang.reflect.Method method) {
        if (method.getAnnotation(DdkAfterCall.class) != null) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length != 1) {
                throw new DdkResourceIllegalityException("In Class: " + bean.getClass().getSimpleName() + ", method: " + method.getName()
                                                         + ", the @DdkAfterCall's parameterTypes is not equal to one , Please Check it!");
            }
            if (parameterTypes[0] != String.class) {
                throw new DdkResourceIllegalityException("In Class: " + bean.getClass().getSimpleName() + ", method: " + method.getName()
                                                         + ", the @DdkAfterCall's parameterTypes is not equal 'java.lang.String' , Please Check it!");
            }
            if (afterCall != null) {
                throw new DdkResourceIllegalityException("In Class: " + bean.getClass().getSimpleName() + ", method: " + method.getName() + ", the @DdkAfterCall's was accessed by "
                                                         + afterCall.getName() + ", but now found new one, the method be modified @DdkAfterCall only has one , Please Check it!");
            } else {
                afterCall = method;
            }
        }
        return afterCall;
    }

    /**
     * 进行全局的加载
     * 1、判断是否具备权限
     * 2、加载字段数据，构建map
     * 3、调用缓存方法
     */
    private synchronized void toLoad() throws IllegalAccessException, InvocationTargetException {
        // 加载配置数据
        ResourceLoadResponseDTO response = loadConfigs();
        // 数据权限验证
        authValidator(response);
    }

    /**
     * 加载远程数据
     */
    private ResourceLoadResponseDTO loadConfigs() {
        String serverIps = ddkProperties.getServerIps();
        int serverPort = ddkProperties.getServerPort();
        String appId = ddkProperties.getAppId();
        String[] serverAddress = serverIps.split(",");
        for (String address : serverAddress) {
            try {
                String reqUrl = String.format(INVOKE_REQUEST_FORMAT, address, serverPort);

                ResourceLoadRequestDTO requestDTO = new ResourceLoadRequestDTO();
                requestDTO.setAppId(appId);

                Client.RpcRequest rpcRequest = new Client.RpcRequest(reqUrl, requestDTO, Maps.newHashMap(), Method.POST, 3, TimeUnit.SECONDS, 3);

                R call = CLIENT.call(rpcRequest, R.class);

                logger.info("[DdkRepositoryHolder][call] Load result:{}", JSONUtil.toJsonStr(call));

                return JSONUtil.toBean(call.getData().get("response").toString(), ResourceLoadResponseDTO.class);
            } catch (Exception ex) {
                logger.error("[DdkRepositoryHolder][call] Ex error message:{}", ThrowableUtil.stackTraceToString(ex));
            }
        }
        throw new ServerOfflineException("All Server IP are Requested, and All failed!!! Please Check your Config, The Server IPs are: " + serverIps);
    }

    /**
     * 权限验证
     * 
     * @param response response
     */
    private void authValidator(ResourceLoadResponseDTO response) throws IllegalAccessException, InvocationTargetException {
        // 字典键
        Set<DictValueObjectDTO> dictValues = response.getDictValues();
        dictAuthValidatorAndAssignment(dictValues);

        // 资源
        Set<ResourceValueObjectDTO> resourceValues = response.getResourceValues();
        resourceAuthValidatorAndInvoke(resourceValues);
    }

    /**
     * 权限验证
     * 
     * @param dictValues dictValues
     */
    private void dictAuthValidatorAndAssignment(Set<DictValueObjectDTO> dictValues) throws IllegalAccessException {
        if (FIELD_MAP.isEmpty()) {
            return;
        }
        if (dictValues == null || CollectionUtils.isEmpty(dictValues)) {
            throw new NoDdkAuthAccessException("The Client Project DDK dict is not Empty, but call Form Server Result is Empty, Please check your it");
        }
        // 遍历执行权限验证和赋值
        // 如果客户端有 && dictValues 没有，则无权限，抛出异常
        // 如果客户端有 && dictValues 有，则有权限，进行赋值
        // 如果客户端没有 && dictValues 没有，则无需处理
        // 如果客户端没有 && dictValues 有，则无需处理
        Map<String, String> dictMap = dictValues.stream().collect(Collectors.toMap(DictValueObjectDTO::getKey, DictValueObjectDTO::getValue));

        // 遍历客户端
        for (FieldHolderKey fieldHolderKey : FIELD_MAP.keySet()) {
            String key = fieldHolderKey.getKey();
            String value = dictMap.get(key);
            if (StringUtils.isBlank(value)) {
                // 客户端有 && dictValues 没有,则无权限，抛出异常
                throw new NoDdkAuthAccessException("The Client Project DDK dict '" + key + "' is not Empty, but call Form Server Result is Empty, Please check your it");
            }
            FieldHolder fieldHolder = FIELD_MAP.get(fieldHolderKey);
            fieldHolder.setValue(value);
            // 字段后置处理
            fieldPostProcessor(fieldHolder);
        }
    }

    /**
     * 字段后置处理器
     * - 1、验证字段类型
     * - 2、对字段进行赋值
     * 
     * @param fieldHolder 字段后置处理器
     */
    private void fieldPostProcessor(FieldHolder fieldHolder) throws IllegalAccessException {
        Class<?> fieldClass = fieldHolder.getFieldClass();
        Field field = fieldHolder.getField();
        String value = fieldHolder.getValue();
        Object bean = fieldHolder.getBean();

        field.setAccessible(true);
        // String 类型
        if (String.class.equals(fieldClass)) {
            field.set(bean, value);
            return;
        }
        // Long 类型
        if (Long.class.equals(fieldClass)) {
            field.set(bean, Long.parseLong(value));
            return;
        }
        // long 类型
        if (long.class.equals(fieldClass)) {
            field.set(bean, Long.parseLong(value));
            return;
        }
        // Double 类型
        if (Double.class.equals(fieldClass)) {
            field.set(bean, Double.parseDouble(value));
            return;
        }
        // double 类型
        if (double.class.equals(fieldClass)) {
            field.set(bean, Double.parseDouble(value));
            return;
        }
        // Integer 类型
        if (Integer.class.equals(fieldClass)) {
            field.set(bean, Integer.parseInt(value));
            return;
        }
        // int 类型
        if (int.class.equals(fieldClass)) {
            field.set(bean, Integer.parseInt(value));
            return;
        }
        // Float
        if (Float.class.equals(fieldClass)) {
            field.set(bean, Float.parseFloat(value));
            return;
        }
        // float
        if (float.class.equals(fieldClass)) {
            field.set(bean, Float.parseFloat(value));
            return;
        }
        // Boolean
        if (Boolean.class.equals(fieldClass)) {
            field.set(bean, Boolean.parseBoolean(value));
            return;
        }
        // boolean
        if (boolean.class.equals(fieldClass)) {
            field.set(bean, Boolean.parseBoolean(value));
            return;
        }
        // Character
        if (Character.class.equals(fieldClass)) {
            // Character 只取0
            field.set(bean, value.charAt(0));
            return;
        }
        // char
        if (char.class.equals(fieldClass)) {
            // Character 只取0
            field.set(bean, value.charAt(0));
            return;
        }
        // Short
        if (Short.class.equals(fieldClass)) {
            field.set(bean, Short.parseShort(value));
            return;
        }
        // short
        if (short.class.equals(fieldClass)) {
            field.set(bean, Short.parseShort(value));
            return;
        }
        // Byte
        if (Byte.class.equals(fieldClass)) {
            field.set(bean, Byte.parseByte(value));
            return;
        }
        // byte
        if (byte.class.equals(fieldClass)) {
            field.set(bean, Byte.parseByte(value));
            return;
        }
        // BigDecimal
        if (BigDecimal.class.equals(fieldClass)) {
            field.set(bean, new BigDecimal(value));
            return;
        }
        throw new IllegalAccessException("Not Support Class Type:" + fieldClass.getName());
    }

    /**
     * 资源权限验证
     * 
     * @param resourceValues resourceValues
     */
    private void resourceAuthValidatorAndInvoke(Set<ResourceValueObjectDTO> resourceValues) throws InvocationTargetException, IllegalAccessException {
        if (METHOD_MAP.isEmpty()) {
            return;
        }
        if (resourceValues == null || CollectionUtils.isEmpty(resourceValues)) {
            throw new NoDdkAuthAccessException("The Client Project DDK resource is not Empty, but call Form Server Result is Empty, Please check your it");
        }
        // 遍历执行权限验证和赋值
        // 如果客户端有 && resourceValues 没有，则无权限，抛出异常
        // 如果客户端有 && resourceValues 有，则有权限，进行赋值
        // 如果客户端没有 && resourceValues 没有，则无需处理
        // 如果客户端没有 && resourceValues 有，则无需处理
        Map<String, String> resourceMap = resourceValues.stream().collect(Collectors.toMap(ResourceValueObjectDTO::getResource, ResourceValueObjectDTO::getValue));

        // 遍历客户端
        for (String key : METHOD_MAP.keySet()) {
            String value = resourceMap.get(key);
            if (StringUtils.isBlank(value)) {
                // 客户端有 && resourceValues 没有,则无权限，抛出异常
                throw new NoDdkAuthAccessException("The Client Project DDK resource '" + key + "' is not Empty, but call Form Server Result is Empty, Please check your it");
            }
            MethodHolder methodHolder = METHOD_MAP.get(key);
            methodHolder.setValue(value);
            // 字段后置处理
            methodInvokePostProcessor(methodHolder);
        }
    }

    /**
     * 方法后置调用
     *
     * @param methodHolder methodHolder
     */
    private void methodInvokePostProcessor(MethodHolder methodHolder) throws InvocationTargetException, IllegalAccessException {
        java.lang.reflect.Method beforeMethodCall = methodHolder.getBeforeMethodCall();
        java.lang.reflect.Method methodCall = methodHolder.getMethodCall();
        java.lang.reflect.Method afterMethodCall = methodHolder.getAfterMethodCall();
        String value = methodHolder.getValue();
        Object bean = methodHolder.getBean();

        if (beforeMethodCall != null) {
            beforeMethodCall.setAccessible(true);
            beforeMethodCall.invoke(bean, value);
        }

        if (methodCall != null) {
            methodCall.setAccessible(true);
            methodCall.invoke(bean, value);
        }

        if (afterMethodCall != null) {
            afterMethodCall.setAccessible(true);
            afterMethodCall.invoke(bean, value);
        }
    }

    /**
     * 刷新缓存数据
     * 
     * @param publish publish
     */
    public synchronized void refresh(PublishValueRefreshDTO publish) throws IllegalAccessException {
        if (publish == null || StringUtils.isBlank(publish.getKey()) || StringUtils.isBlank(publish.getValue())) {
            throw new DdkValueIllegalityException("refresh field error! the PublishValueRefreshDTO is not right");
        }
        Set<FieldHolderKey> targetKeys = FIELD_MAP.keySet().stream().filter(key -> StringUtils.equals(key.getKey(), publish.getKey())).collect(Collectors.toSet());
        for (FieldHolderKey targetKey : targetKeys) {
            FieldHolder fieldHolder = FIELD_MAP.get(targetKey);
            if (fieldHolder == null) {
                logger.info("[DdkRepositoryHolder][refresh] not need refresh! key:{},value:{}", publish.getKey(), publish.getValue());
                return;
            }
            fieldHolder.setValue(publish.getValue());

            fieldPostProcessor(fieldHolder);
        }
    }

    /**
     * 调用执行的方法
     * 
     * @param publish publish
     */
    public synchronized void invoke(PublishMethodInvokeDTO publish) throws InvocationTargetException, IllegalAccessException {
        if (publish == null || StringUtils.isBlank(publish.getNoticeResource()) || StringUtils.isBlank(publish.getNoticeValue())) {
            throw new DdkValueIllegalityException("refresh field error! the PublishMethodInvokeDTO is not right");
        }
        MethodHolder methodHolder = METHOD_MAP.get(publish.getNoticeResource());
        if (methodHolder == null) {
            logger.info("[DdkRepositoryHolder][invoke] not need invoke! key:{},value:{}", publish.getNoticeResource(), publish.getNoticeValue());
            return;
        }
        methodHolder.setValue(publish.getNoticeValue());

        methodInvokePostProcessor(methodHolder);
    }
}
